#pragma once
#include <string>
#include <functional>
#include <memory>
#include <time.h>
#include <map>
#include <vector>
#include <mutex>
#include <atomic>

namespace rtbus {
class RtCommuItf
{
public:
    struct BusCfg
    {
        int dev_id;
        int channel_id;
        std::string adapter_name;
    };
    struct SlaveInfo
    {
        bool enable_dc;
        uint8_t slaveid;
        uint32_t vendor_id;
        uint32_t product_code;
        uint32_t revision_number;
        int32_t cycletime_us;
        int32_t shifttime_us;
    };

public:
    virtual ~RtCommuItf();
    /**
     * @brief 打开并检验物理接口
     * @note 阻塞的接口，必须运行在非实时上下文中
     */
    virtual int open(BusCfg cfg) = 0;

    /**
     * @brief 关闭并释放物理接口
     * @note 阻塞的接口，必须运行在非实时上下文中
     */
    virtual void close() = 0;

    /**
     * @brief 配置并启动协议栈，成功后pdo通讯开始
     * @note 阻塞的接口，必须运行在非实时上下文中
     */
    virtual int active() = 0;

    /**
     * @brief 结束pdo通讯
     * @note 阻塞的接口，必须运行在非实时上下文中
     */
    virtual int deactive() = 0;

    /**
     * @brief 注册sdo处理的ID号，主要用于CAN类型总线
     */
    virtual int
    register_sdo_handle(uint8_t slave_id, uint32_t write_req_frame_id, uint32_t read_req_frame_id, uint32_t resp_frame_id, size_t obj_id_size = 2) = 0;

    virtual void send() = 0;
    virtual void recv(int64_t* timeoff) = 0;
    virtual void reset() = 0;
    virtual bool is_actived() = 0;
    /**
     * @brief Get the sdo object
     * 
     * @param slave_id 从站号
     * @param obj_id 对象编号，不同的类型的设备有不同的组织方式，在实际报文中高字节在前低字节在后，即0x1234的objid在报文中为0x12 0x34
     * @param cb 当收到frameid,slaveid和obji匹配时调用，若cb返回非0，则继续等待匹配数据
     * @param timeout 发送请求后等待响应的时间，注意是真正发送出报文后的时间，不包括在队列中等待的时间，<=0为一直等待，单位ms，默认为80ms
     * @param retry 超时后重新发送的次数，<0为一直重发, 0为不重发，默认为3次
     * @return int 0为成功，-1为有重复的报文
     * @note 对于timeout和retry要慎重设置，过小可能导致sdo失败，过大可能导致sdo队列阻塞
     */
    virtual int get_sdo(uint8_t slave_id, uint32_t obj_id, std::function<int(uint8_t*, int)> cb, int timeout = 80, int retry = 3) = 0;

    /**
     * @brief Get the sdo object
     * 
     * @param slave_id 从站号
     * @param obj_id 对象编号，不同的类型的设备有不同的组织方式，在实际报文中高字节在前低字节在后，即0x1234的objid在报文中为0x12 0x34
     * @param cb 当收到frameid,slaveid和obji匹配时调用，若cb返回非0，则继续等待匹配数据
     * @param is_em 暂停当前sdo，插入紧急发送
     * @param timeout 发送请求后等待响应的时间，注意是真正发送出报文后的时间，不包括在队列中等待的时间，<=0为一直等待，单位ms，默认为80ms
     * @param retry 超时后重新发送的次数，<0为一直重发, 0为不重发，默认为3次
     * @return int 0为成功，-1为有重复的报文
     * @note 对于timeout和retry要慎重设置，过小可能导致sdo失败，过大可能导致sdo队列阻塞
     */
    virtual int
    set_sdo(uint8_t slave_id, uint32_t obj_id, uint8_t val[4], size_t size, std::function<int(uint8_t*, int)> cb, int timeout = 80, int retry = 3) = 0;

    /**
     * @brief 阻塞式地获取sdo对象数据
     * @param slave_id 从站号
     * @param objid 对象编号
     * @param data 接收数据缓冲区
     * @param s 接收数据缓冲区大小
     * @param retry 重试次数，若<0则一直重试
     */
    virtual int get_sdo_blocking(uint8_t slave_id, uint32_t objid, void* data, int data_len, int retry = 3) = 0;
    /**
     * @brief 阻塞式地发送sdo对象数据
     * @param slave_id 从站号
     * @param objid 对象编号
     * @param data 待发送数据缓冲区
     * @param s 发送数据缓冲区大小
     * @param respdata 返回的响应数据，若为nullptr则不接收响应数据
     * @param retry 重试次数，若<0则一直重试
     */
    virtual int set_sdo_blocking(uint8_t slave_id, uint32_t objid, void* data, int s, void* respdata = nullptr, int retry = 3);

    inline void set_name(std::string name) { name_ = name; }
    inline std::string get_name() const { return name_; }
    inline void set_bus_id(int id) { bus_id_ = id; }
    inline int get_bus_id() const { return bus_id_; }

    virtual int config_slave(SlaveInfo& info);
    virtual int config_txpdo(uint32_t obj, size_t size);
    virtual int config_rxpdo(uint32_t obj, size_t size);
    virtual int set_txpdo_data(uint32_t obj, uint8_t* data);
    virtual int get_rxpdo_data(uint32_t obj, uint8_t* data);
    virtual void sync_time(RtCommuItf* slave);
    virtual bool is_can();

protected:
    struct PdoObjConfig
    {
        uint32_t obj_id;
        size_t size;
        uint8_t data[8];

        bool is_tx;
        uint8_t alive_cnt;
    };
    std::map<uint32_t, PdoObjConfig> txpdo_, rxpdo_;

    std::vector<SlaveInfo> slaves_info_;
    std::string name_;
    int bus_id_;
    friend class BusMgr;
};

class BusMgr
{
public:
    BusMgr();
    ~BusMgr();
    static BusMgr* get();
    static RtCommuItf::BusCfg parser_interface(std::string type, std::string interface);
    std::shared_ptr<RtCommuItf> create_bus(std::string type);
    int init_all_bus();
    int append_bus(std::shared_ptr<RtCommuItf> bus);
    void enable_device(bool en = true) { is_dev_enabled_ = en; }
    void hook_function(std::function<void(void)> f);
    std::shared_ptr<RtCommuItf> bus(std::string name)
    {
        for (auto& bus : all_bus_)
        {
            if (bus->get_name() == name)
                return bus;
        }
        return nullptr;
    }
    void close();
    void update(int64_t* timeoff);
    inline int get_interval_us() const { return interval_us_; }

private:
    std::vector<std::shared_ptr<RtCommuItf>> all_bus_;
    int interval_us_;
    std::atomic_bool is_dev_enabled_;
    std::mutex mtx_;
};

void schedule_motion();
void schedule_wait();
int check_motion_ack();
void schedule_send_ack();
}  // namespace rtbus
